﻿using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;

namespace RayDen.RayEngine.Engines.ProgressivePhotonMapping
{
    public class PPMEyePath : BasePPMPath
    {
        private RgbSpectrum Throughput;
        public override void InitPath(IPathProcessor buffer)
        {
            base.InitPath(buffer);
            IRay ray;
            pathIntegrator.Scene.Camera.GetRay(Sample.imageX, Sample.imageY, out ray);
            this.PathRay = (RayData)ray;
            Throughput = new RgbSpectrum(1f);
        }

        public override void Advance(RayBuffer rayBuffer, SampleBuffer consumer)
        {
            PPMHitPoint hp;
            base.Advance(rayBuffer, consumer);
            var rayHit = rayBuffer.rayHits[RayIndex];
            depth++;
            var wo = -PathRay.Dir;
            bool missed = rayHit.Miss();
            if (missed || depth > scene.MaxPathDepth)
            {
                hp = new PPMHitPoint
                {
                    type = HitPointType.Constant,
                    scrX = (float)Sample.imageX,
                    scrY = (float)Sample.imageY,
                    throughput = scene.EnvironmentMap != null & missed
                                                 ? (RgbSpectrum)(RgbSpectrumInfo)(scene.EnvironmentMap.Le(ref wo)) * Throughput
                                                 : Throughput,
                    pass = Sample.pass
                };
                this.engine.AddHitPoint(hp);
                this.InitPath(pathIntegrator);
                return;
            }
            var hitPoint = PathRay.Point(rayHit.Distance);
            if (hitInfo == null)
            {
                hitInfo = new SurfaceIntersectionData(ref rayHit, true);
            }
            SurfaceSampler.GetIntersection(ref PathRay, ref rayHit, ref hitInfo);
            var currentTriangleIndex = (int)rayHit.Index;

            if (hitInfo.IsLight)
            {
                hp = new PPMHitPoint();
                hp.type = HitPointType.Constant;
                hp.scrX = (float)Sample.imageX;
                hp.scrY = (float)Sample.imageY;
                hp.pass = Sample.pass;


                var lt = scene.GetLightByIndex(currentTriangleIndex);

                hp.throughput = (RgbSpectrum)(RgbSpectrumInfo)(lt.Le(ref wo)) * Throughput;
                engine.AddHitPoint(hp);
                this.InitPath(pathIntegrator);
                return;
            }

            float fPdf;
            Vector wi;
            bool specularBounce;
            var bsdf = hitInfo.MMaterial;
            RgbSpectrum f = bsdf.Sample_f(ref wo, out wi, ref hitInfo.Normal, ref hitInfo.ShadingNormal, ref Throughput,
                                                    Sample.GetLazyValue(), Sample.GetLazyValue(),
                                                    Sample.GetLazyValue(), ref hitInfo.TextureData,
                                                    out fPdf, out specularBounce);
            if (fPdf <= 0f || f.IsBlack())
            {
                hp = new PPMHitPoint();
                hp.type = HitPointType.Constant;
                hp.scrX = (float)Sample.imageX;
                hp.scrY = (float)Sample.imageY;
                hp.pass = Sample.pass;

                engine.AddHitPoint(hp);
                this.InitPath(pathIntegrator);
                return;
            }

            if (specularBounce || !bsdf.IsDiffuse())
            {
                Throughput *= f / fPdf;
                PathRay = new RayData(hitPoint, wi);
                return;
            }

            hp = new PPMHitPoint();
            hp.type = HitPointType.Surface;
            hp.scrX = (float)Sample.imageX;
            hp.scrY = (float)Sample.imageY;
            hp.material = bsdf;
            hp.throughput = f*Throughput;
            hp.pass = Sample.pass;
            hp.position = hitPoint;
            hp.wo = wo;
            hp.normal = hitInfo.ShadingNormal;
            engine.AddHitPoint(hp);
            this.InitPath(pathIntegrator);
        }

        public override void Splat(SampleBuffer sampleBuffer)
        {

        }
    }
}
